{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Bring Your Own R Algorithm\n",
    "_**Create a Docker container for training R algorithms and hosting R models**_\n",
    "\n",
    "---\n",
    "\n",
    "---\n",
    "\n",
    "## Contents\n",
    "\n",
    "1. [Background](#Background)\n",
    "1. [Preparation](#Preparation)\n",
    "1. [Code](#Code)\n",
    "  1. [Fit](#Fit)\n",
    "  1. [Serve](#Serve)\n",
    "  1. [Dockerfile](#Dockerfile)\n",
    "  1. [Publish](#Publish)\n",
    "1. [Data](#Data)\n",
    "1. [Train](#Train)\n",
    "1. [Host](#Host)\n",
    "1. [Predict](#Predict)\n",
    "1. [Extensions](#Extensions)\n",
    "\n",
    "---\n",
    "## Background\n",
    "\n",
    "R is a popular open source statistical programming language, with a lengthy history in Data Science and Machine Learning.  The breadth of algorithms available as an R package is impressive, which fuels a growing community of users.  The R kernel can be installed into Amazon SageMaker Notebooks, and Docker containers which use R can be used to take advantage of Amazon SageMaker's flexible training and hosting functionality.  This notebook illustrates a simple use case for creating an R container and then using it to train and host a model.  In order to take advantage of boto, we'll use Python within the notebook, but this could be done 100% in R by invoking command line arguments.\n",
    "\n",
    "---\n",
    "## Preparation\n",
    "\n",
    "_This notebook was created and tested on an ml.m4.xlarge notebook instance._\n",
    "\n",
    "Let's start by specifying:\n",
    "\n",
    "- The S3 bucket and prefix that you want to use for training and model data.  This should be within the same region as the Notebook Instance, training, and hosting.\n",
    "- The IAM role arn used to give training and hosting access to your data. See the documentation for how to create these.  Note, if more than one role is required for notebook instances, training, and/or hosting, please replace the boto regexp with a the appropriate full IAM role arn string(s)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "isConfigCell": true
   },
   "outputs": [],
   "source": [
    "bucket = '<your_s3_bucket_name_here>'\n",
    "prefix = 'sagemaker/DEMO-r-byo'\n",
    " \n",
    "# Define IAM role\n",
    "import boto3\n",
    "import re\n",
    "from sagemaker import get_execution_role\n",
    "\n",
    "role = get_execution_role()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we'll import the libraries we'll need for the remainder of the notebook."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import time\n",
    "import json\n",
    "import os\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Permissions\n",
    "\n",
    "Running this notebook requires permissions in addition to the normal `SageMakerFullAccess` permissions. This is because we'll be creating a new repository in Amazon ECR. The easiest way to add these permissions is simply to add the managed policy `AmazonEC2ContainerRegistryFullAccess` to the role that you used to start your notebook instance. There's no need to restart your notebook instance when you do this, the new permissions will be available immediately.\n",
    "\n",
    "---\n",
    "## Code\n",
    "\n",
    "For this example, we'll need 3 supporting code files.\n",
    "\n",
    "### Fit\n",
    "\n",
    "`mars.R` creates functions to fit and serve our model.  The algorithm we've chosen to use is [Multivariate Adaptive Regression Splines](https://en.wikipedia.org/wiki/Multivariate_adaptive_regression_splines).  This is a suitable example as it's a unique and powerful algorithm, but isn't as broadly used as Amazon SageMaker algorithms, and it isn't available in Python's scikit-learn library.  R's repository of packages is filled with algorithms that share these same criteria. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "_The top of the code is devoted to setup.  Bringing in the libraries we'll need and setting up the file paths as detailed in Amazon SageMaker documentation on bringing your own container._\n",
    "\n",
    "```\n",
    "# Bring in library that contains multivariate adaptive regression splines (MARS)\n",
    "library(mda)\n",
    "\n",
    "# Bring in library that allows parsing of JSON training parameters\n",
    "library(jsonlite)\n",
    "\n",
    "# Bring in library for prediction server\n",
    "library(plumber)\n",
    "\n",
    "\n",
    "# Setup parameters\n",
    "# Container directories\n",
    "prefix <- '/opt/ml'\n",
    "input_path <- paste(prefix, 'input/data', sep='/')\n",
    "output_path <- paste(prefix, 'output', sep='/')\n",
    "model_path <- paste(prefix, 'model', sep='/')\n",
    "param_path <- paste(prefix, 'input/config/hyperparameters.json', sep='/')\n",
    "\n",
    "# Channel holding training data\n",
    "channel_name = 'train'\n",
    "training_path <- paste(input_path, channel_name, sep='/')\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "_Next, we define a train function that actually fits the model to the data.  For the most part this is idiomatic R, with a bit of maneuvering up front to take in parameters from a JSON file, and at the end to output a success indicator._\n",
    "\n",
    "```\n",
    "# Setup training function\n",
    "train <- function() {\n",
    "\n",
    "    # Read in hyperparameters\n",
    "    training_params <- read_json(param_path)\n",
    "\n",
    "    target <- training_params$target\n",
    "\n",
    "    if (!is.null(training_params$degree)) {\n",
    "        degree <- as.numeric(training_params$degree)}\n",
    "    else {\n",
    "        degree <- 2}\n",
    "\n",
    "    # Bring in data\n",
    "    training_files = list.files(path=training_path, full.names=TRUE)\n",
    "    training_data = do.call(rbind, lapply(training_files, read.csv))\n",
    "    \n",
    "    # Convert to model matrix\n",
    "    training_X <- model.matrix(~., training_data[, colnames(training_data) != target])\n",
    "\n",
    "    # Save factor levels for scoring\n",
    "    factor_levels <- lapply(training_data[, sapply(training_data, is.factor), drop=FALSE],\n",
    "                            function(x) {levels(x)})\n",
    "    \n",
    "    # Run multivariate adaptive regression splines algorithm\n",
    "    model <- mars(x=training_X, y=training_data[, target], degree=degree)\n",
    "    \n",
    "    # Generate outputs\n",
    "    mars_model <- model[!(names(model) %in% c('x', 'residuals', 'fitted.values'))]\n",
    "    attributes(mars_model)$class <- 'mars'\n",
    "    save(mars_model, factor_levels, file=paste(model_path, 'mars_model.RData', sep='/'))\n",
    "    print(summary(mars_model))\n",
    "\n",
    "    write.csv(model$fitted.values, paste(output_path, 'data/fitted_values.csv', sep='/'), row.names=FALSE)\n",
    "    write('success', file=paste(output_path, 'success', sep='/'))}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "_Then, we setup the serving function (which is really just a short wrapper around our plumber.R file that we'll discuss [next](#Serve)._\n",
    "\n",
    "```\n",
    "# Setup scoring function\n",
    "serve <- function() {\n",
    "    app <- plumb(paste(prefix, 'plumber.R', sep='/'))\n",
    "    app$run(host='0.0.0.0', port=8080)}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "_Finally, a bit of logic to determine if, based on the options passed when Amazon SageMaker Training or Hosting call this script, we are using the container to train an algorithm or host a model._\n",
    "\n",
    "```\n",
    "# Run at start-up\n",
    "args <- commandArgs()\n",
    "if (any(grepl('train', args))) {\n",
    "    train()}\n",
    "if (any(grepl('serve', args))) {\n",
    "    serve()}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Serve\n",
    "`plumber.R` uses the [plumber](https://www.rplumber.io/) package to create a lightweight HTTP server for processing requests in hosting.  Note the specific syntax, and see the plumber help docs for additional detail on more specialized use cases."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Per the Amazon SageMaker documentation, our service needs to accept post requests to ping and invocations.  plumber specifies this with custom comments, followed by functions that take specific arguments.\n",
    "\n",
    "Here invocations does most of the work, ingesting our trained model, handling the HTTP request body, and producing a CSV output of predictions.\n",
    "\n",
    "```\n",
    "# plumber.R\n",
    "\n",
    "\n",
    "#' Ping to show server is there\n",
    "#' @get /ping\n",
    "function() {\n",
    "    return('')}\n",
    "\n",
    "\n",
    "#' Parse input and return the prediction from the model\n",
    "#' @param req The http request sent\n",
    "#' @post /invocations\n",
    "function(req) {\n",
    "\n",
    "    # Setup locations\n",
    "    prefix <- '/opt/ml'\n",
    "    model_path <- paste(prefix, 'model', sep='/')\n",
    "\n",
    "    # Bring in model file and factor levels\n",
    "    load(paste(model_path, 'mars_model.RData', sep='/'))\n",
    "\n",
    "    # Read in data\n",
    "    conn <- textConnection(gsub('\\\\\\\\n', '\\n', req$postBody))\n",
    "    data <- read.csv(conn)\n",
    "    close(conn)\n",
    "\n",
    "    # Convert input to model matrix\n",
    "    scoring_X <- model.matrix(~., data, xlev=factor_levels)\n",
    "\n",
    "    # Return prediction\n",
    "    return(paste(predict(mars_model, scoring_X, row.names=FALSE), collapse=','))}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Dockerfile\n",
    "\n",
    "Smaller containers are preferred for Amazon SageMaker as they lead to faster spin up times in training and endpoint creation, so this container is kept minimal.  It simply starts with Ubuntu, installs R, mda, and plumber libraries, then adds `mars.R` and `plumber.R`, and finally runs `mars.R` when the entrypoint is launched.\n",
    "\n",
    "```Dockerfile\n",
    "FROM ubuntu:16.04\n",
    "\n",
    "MAINTAINER Amazon SageMaker Examples <amazon-sagemaker-examples@amazon.com>\n",
    "\n",
    "RUN apt-get -y update && apt-get install -y --no-install-recommends \\\n",
    "    wget \\\n",
    "    r-base \\\n",
    "    r-base-dev \\\n",
    "    ca-certificates\n",
    "\n",
    "RUN R -e \"install.packages(c('mda', 'plumber'), repos='https://cloud.r-project.org')\"\n",
    "\n",
    "COPY mars.R /opt/ml/mars.R\n",
    "COPY plumber.R /opt/ml/plumber.R\n",
    "\n",
    "ENTRYPOINT [\"/usr/bin/Rscript\", \"/opt/ml/mars.R\", \"--no-save\"]\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Publish\n",
    "Now, to publish this container to ECR, we'll run the comands below.\n",
    "\n",
    "This command will take several minutes to run the first time."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%sh\n",
    "\n",
    "# The name of our algorithm\n",
    "algorithm_name=sagemaker-rmars\n",
    "\n",
    "#set -e # stop if anything fails\n",
    "\n",
    "account=$(aws sts get-caller-identity --query Account --output text)\n",
    "\n",
    "# Get the region defined in the current configuration (default to us-west-2 if none defined)\n",
    "region=$(aws configure get region)\n",
    "region=${region:-us-west-2}\n",
    "\n",
    "fullname=\"${account}.dkr.ecr.${region}.amazonaws.com/${algorithm_name}:latest\"\n",
    "\n",
    "# If the repository doesn't exist in ECR, create it.\n",
    "\n",
    "aws ecr describe-repositories --repository-names \"${algorithm_name}\" > /dev/null 2>&1\n",
    "\n",
    "if [ $? -ne 0 ]\n",
    "then\n",
    "    aws ecr create-repository --repository-name \"${algorithm_name}\" > /dev/null\n",
    "fi\n",
    "\n",
    "# Get the login command from ECR and execute it directly\n",
    "$(aws ecr get-login --region ${region} --no-include-email)\n",
    "\n",
    "# Build the docker image locally with the image name and then push it to ECR\n",
    "# with the full name.\n",
    "docker build  -t ${algorithm_name} .\n",
    "docker tag ${algorithm_name} ${fullname}\n",
    "\n",
    "docker push ${fullname}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## Data\n",
    "For this illustrative example, we'll simply use `iris`.  This a classic, but small, dataset used to test supervised learning algorithms.  Typically the goal is to predict one of three flower species based on various measurements of the flowers' attributes.  Further detail can be found [here](https://en.wikipedia.org/wiki/Iris_flower_data_set).\n",
    "\n",
    "Then let's copy the data to S3."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_file = 'iris.csv'\n",
    "boto3.Session().resource('s3').Bucket(bucket).Object(os.path.join(prefix, 'train', train_file)).upload_file(train_file)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "_Note: Although we could, we'll avoid doing any preliminary transformations on the data, instead choosing to do those transformations inside the container.  This is not typically the best practice for model efficiency, but provides some benefits in terms of flexibility._"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## Train\n",
    "\n",
    "Now, let's setup the information needed to train a Multivariate Adaptive Regression Splines (MARS) model on iris data.  In this case, we'll predict `Sepal.Length` rather than the more typical classification of `Species` to show how factors might be included in a model and limit the case to regression.\n",
    "\n",
    "First, we'll get our region and account information so that we can point to the ECR container we just created."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "region = boto3.Session().region_name\n",
    "account = boto3.client('sts').get_caller_identity().get('Account')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "- Specify the role to use\n",
    "- Give the training job a name\n",
    "- Point the algorithm to the container we created\n",
    "- Specify training instance resources (in this case our algorithm is only single-threaded so stick to 1 instance)\n",
    "- Point to the S3 location of our input data and the `train` channel expected by our algorithm\n",
    "- Point to the S3 location for output\n",
    "- Provide hyperparamters (keeping it simple)\n",
    "- Maximum run time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "r_job = 'DEMO-r-byo-' + time.strftime(\"%Y-%m-%d-%H-%M-%S\", time.gmtime())\n",
    "\n",
    "print(\"Training job\", r_job)\n",
    "\n",
    "r_training_params = {\n",
    "    \"RoleArn\": role,\n",
    "    \"TrainingJobName\": r_job,\n",
    "    \"AlgorithmSpecification\": {\n",
    "        \"TrainingImage\": '{}.dkr.ecr.{}.amazonaws.com/sagemaker-rmars:latest'.format(account, region),\n",
    "        \"TrainingInputMode\": \"File\"\n",
    "    },\n",
    "    \"ResourceConfig\": {\n",
    "        \"InstanceCount\": 1,\n",
    "        \"InstanceType\": \"ml.m4.xlarge\",\n",
    "        \"VolumeSizeInGB\": 10\n",
    "    },\n",
    "    \"InputDataConfig\": [\n",
    "        {\n",
    "            \"ChannelName\": \"train\",\n",
    "            \"DataSource\": {\n",
    "                \"S3DataSource\": {\n",
    "                    \"S3DataType\": \"S3Prefix\",\n",
    "                    \"S3Uri\": \"s3://{}/{}/train\".format(bucket, prefix),\n",
    "                    \"S3DataDistributionType\": \"FullyReplicated\"\n",
    "                }\n",
    "            },\n",
    "            \"CompressionType\": \"None\",\n",
    "            \"RecordWrapperType\": \"None\"\n",
    "        }\n",
    "    ],\n",
    "    \"OutputDataConfig\": {\n",
    "        \"S3OutputPath\": \"s3://{}/{}/output\".format(bucket, prefix)\n",
    "    },\n",
    "    \"HyperParameters\": {\n",
    "        \"target\": \"Sepal.Length\",\n",
    "        \"degree\": \"2\"\n",
    "    },\n",
    "    \"StoppingCondition\": {\n",
    "        \"MaxRuntimeInSeconds\": 60 * 60\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's kick off our training job on Amazon SageMaker Training, using the parameters we just created.  Because training is managed (AWS takes care of spinning up and spinning down the hardware), we don't have to wait for our job to finish to continue, but for this case, let's setup a waiter so we can monitor the status of our training."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "\n",
    "sm = boto3.client('sagemaker')\n",
    "sm.create_training_job(**r_training_params)\n",
    "\n",
    "status = sm.describe_training_job(TrainingJobName=r_job)['TrainingJobStatus']\n",
    "print(status)\n",
    "sm.get_waiter('training_job_completed_or_stopped').wait(TrainingJobName=r_job)\n",
    "status = sm.describe_training_job(TrainingJobName=r_job)['TrainingJobStatus']\n",
    "print(\"Training job ended with status: \" + status)\n",
    "if status == 'Failed':\n",
    "    message = sm.describe_training_job(TrainingJobName=r_job)['FailureReason']\n",
    "    print('Training failed with the following error: {}'.format(message))\n",
    "    raise Exception('Training job failed')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## Host\n",
    "\n",
    "Hosting the model we just trained takes three steps in Amazon SageMaker.  First, we define the model we want to host, pointing the service to the model artifact our training job just wrote to S3."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "r_hosting_container = {\n",
    "    'Image': '{}.dkr.ecr.{}.amazonaws.com/sagemaker-rmars:latest'.format(account, region),\n",
    "    'ModelDataUrl': sm.describe_training_job(TrainingJobName=r_job)['ModelArtifacts']['S3ModelArtifacts']\n",
    "}\n",
    "\n",
    "create_model_response = sm.create_model(\n",
    "    ModelName=r_job,\n",
    "    ExecutionRoleArn=role,\n",
    "    PrimaryContainer=r_hosting_container)\n",
    "\n",
    "print(create_model_response['ModelArn'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, let's create an endpoing configuration, passing in the model we just registered.  In this case, we'll only use a few c4.xlarges."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "r_endpoint_config = 'DEMO-r-byo-config-' + time.strftime(\"%Y-%m-%d-%H-%M-%S\", time.gmtime())\n",
    "print(r_endpoint_config)\n",
    "create_endpoint_config_response = sm.create_endpoint_config(\n",
    "    EndpointConfigName=r_endpoint_config,\n",
    "    ProductionVariants=[{\n",
    "        'InstanceType': 'ml.m4.xlarge',\n",
    "        'InitialInstanceCount': 1,\n",
    "        'ModelName': r_job,\n",
    "        'VariantName': 'AllTraffic'}])\n",
    "\n",
    "print(\"Endpoint Config Arn: \" + create_endpoint_config_response['EndpointConfigArn'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, we'll create the endpoints using our endpoint configuration from the last step."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "\n",
    "r_endpoint = 'DEMO-r-endpoint-' + time.strftime(\"%Y%m%d%H%M\", time.gmtime())\n",
    "print(r_endpoint)\n",
    "create_endpoint_response = sm.create_endpoint(\n",
    "    EndpointName=r_endpoint,\n",
    "    EndpointConfigName=r_endpoint_config)\n",
    "print(create_endpoint_response['EndpointArn'])\n",
    "\n",
    "resp = sm.describe_endpoint(EndpointName=r_endpoint)\n",
    "status = resp['EndpointStatus']\n",
    "print(\"Status: \" + status)\n",
    "\n",
    "try:\n",
    "    sm.get_waiter('endpoint_in_service').wait(EndpointName=r_endpoint)\n",
    "finally:\n",
    "    resp = sm.describe_endpoint(EndpointName=r_endpoint)\n",
    "    status = resp['EndpointStatus']\n",
    "    print(\"Arn: \" + resp['EndpointArn'])\n",
    "    print(\"Status: \" + status)\n",
    "\n",
    "    if status != 'InService':\n",
    "        raise Exception('Endpoint creation did not succeed')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## Predict\n",
    "To confirm our endpoints are working properly, let's try to invoke the endpoint.\n",
    "\n",
    "_Note: The payload we're passing in the request is a CSV string with a header record, followed by multiple new lines.  It also contains text columns, which the serving code converts to the set of indicator variables needed for our model predictions.  Again, this is not a best practice for highly optimized code, however, it showcases the flexibility of bringing your own algorithm._"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "iris = pd.read_csv('iris.csv')\n",
    "\n",
    "runtime = boto3.Session().client('runtime.sagemaker')\n",
    "\n",
    "payload = iris.drop(['Sepal.Length'], axis=1).to_csv(index=False)\n",
    "response = runtime.invoke_endpoint(EndpointName=r_endpoint,\n",
    "                                   ContentType='text/csv',\n",
    "                                   Body=payload)\n",
    "\n",
    "result = json.loads(response['Body'].read().decode())\n",
    "result "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can see the result is a CSV of predictions for our target variable.  Let's compare them to the actuals to see how our model did."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.scatter(iris['Sepal.Length'], np.fromstring(result[0], sep=','))\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "## Extensions\n",
    "\n",
    "This notebook showcases a straightforward example to train and host an R algorithm in Amazon SageMaker.  As mentioned previously, this notebook could also be written in R.  We could even train the algorithm entirely within a notebook and then simply use the serving portion of the container to host our model.\n",
    "\n",
    "Other extensions could include setting up the R algorithm to train in parallel.  Although R is not the easiest language to build distributed applications on top of, this is possible.  In addition, running multiple versions of training simultaneously would allow for parallelized grid (or random) search for optimal hyperparamter settings.  This would more fully realize the benefits of managed training."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### (Optional) Clean-up\n",
    "\n",
    "If you're ready to be done with this notebook, please run the cell below.  This will remove the hosted endpoint you created and avoid any charges from a stray instance being left on."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sm.delete_endpoint(EndpointName=r_endpoint)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Environment (conda_python3)",
   "language": "python",
   "name": "conda_python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.3"
  },
  "notice": "Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.  Licensed under the Apache License, Version 2.0 (the \"License\"). You may not use this file except in compliance with the License. A copy of the License is located at http://aws.amazon.com/apache2.0/ or in the \"license\" file accompanying this file. This file is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License."
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
